package com.legstar.talend.xsd2xml;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * The raw Tos Schema produced might need further manipulation.
 * <p/>
 * One such post processing is splitting a Tos Schema into multiple schemas.
 * 
 */
public class Cob2TosSchemaPostProcessor {

    /** Detects a choice tag in a column label. */
    private static final Pattern CHOICE_PREFIX_PATTERN = Pattern
            .compile("\\[choice:((\\w+)\\_(\\w+))\\](.*)");

    /**
     * Split the raw TOS schema into a set of schemas each corresponding to a
     * combination of alternatives.
     * 
     * @param rawTosSchema the raw TOS schema
     * @return a set of schemas where alternatives have been filtered. There is
     *         one schema per combination of alternatives.
     */
    public Map<String, TosSchema> splitChoices(final TosSchema rawTosSchema) {
        Map<String, TosSchema> result = new LinkedHashMap<String, TosSchema>();

        List<Choice> choices = getChoices(rawTosSchema);
        if (choices.size() == 0) {
            result.put("", rawTosSchema);
            return result;
        }

        boolean gotmore = true;
        while (gotmore) {
            List<String> selectors = new LinkedList<String>();
            gotmore = getNextselector(choices, 0, selectors);
            TosSchema tosSchema = filter(rawTosSchema, selectors);
            result.put(getSchemaId(selectors), tosSchema);
        }

        return result;
    }

    /**
     * Create a set of selectors to help selecting a combination of choice and
     * alternatives.
     * 
     * @param choices the list of available choices
     * @param choiceIndex the index within the list of choices
     * @param selectors the list of selectors being built
     * @return true if there are more alternatives to process in the choice
     *         identified by choiceIndex
     */
    public boolean getNextselector(final List<Choice> choices,
            final int choiceIndex, final List<String> selectors) {

        boolean gotmore = false;
        Choice choice = choices.get(choiceIndex);
        int cai = choice.getCurrentAlternativeIndex();
        String selector = choice.getId() + "_"
                + choice.getAlternatives().get(cai);
        selectors.add(selector);
        if (choiceIndex < choices.size() - 1) {
            gotmore = getNextselector(choices, choiceIndex + 1, selectors);
        }
        if (!gotmore) {
            cai++;
            if (cai < choice.getAlternatives().size()) {
                choice.setCurrentAlternativeIndex(cai);
                gotmore = true;
            } else {
                choice.setCurrentAlternativeIndex(0);
                gotmore = false;
            }
        }
        return gotmore;

    }

    /**
     * Given a choice selector filter, this method filters out all alternatives
     * to choices which are not part of the selector.
     * 
     * @param rawTosSchema the raw TosSchema with columns marked for choice
     *        selection
     * @param selector a list of selectors which match the
     *        choiceid_alternativeid pattern.
     * @return a new schema where onluy selected alternatives are present
     */
    public TosSchema filter(final TosSchema rawTosSchema, List<String> selectors) {
        TosSchema filteredSchema = new TosSchema();
        for (TosSchemaColumn column : rawTosSchema.getColumns()) {
            Matcher matcher = CHOICE_PREFIX_PATTERN.matcher(column.getLabel());
            if (matcher.matches()) {
                for (String selector : selectors) {
                    if (matcher.group(1).equals(selector)) {
                        TosSchemaColumn clone = column.clone();
                        clone.setLabel(matcher.group(4));
                        clone.setOriginalDbColumnName(matcher.group(4));
                        filteredSchema.addSchemaColumn(clone);
                        break;
                    }
                }

            } else {
                filteredSchema.addSchemaColumn(column.clone());
            }
        }
        return filteredSchema;
    }

    /**
     * Build a unique ID for a schema filtered for a set of choices and
     * alternatives.
     * 
     * @param selectors the set of choices and alternatives
     * @return a unique name for the filtered schema
     */
    public String getSchemaId(List<String> selectors) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (String selector : selectors) {
            if (!first) {
                sb.append("_");
            } else {
                first = false;
            }
            sb.append(selector);
        }
        return sb.toString();
    }

    /**
     * The raw schema contains all columns for all choice alternatives but these
     * are marked with a choice id and alternative id. Here we create the
     * ordered list of choices along with their alternatives.
     * 
     * @param rawTosSchema the raw TOS schema
     * @return a list of choices (empty list if there are no choices)
     */
    public List<Choice> getChoices(final TosSchema rawTosSchema) {

        Choice lastChoice = null;

        List<Choice> choices = new LinkedList<Choice>();
        for (TosSchemaColumn column : rawTosSchema.getColumns()) {
            Matcher matcher = CHOICE_PREFIX_PATTERN.matcher(column.getLabel());
            if (matcher.matches()) {
                String choiceId = matcher.group(2);
                String alternativeId = matcher.group(3);
                if (lastChoice == null || !choiceId.equals(lastChoice.getId())) {
                    Choice choice = new Choice(choiceId);
                    choices.add(choice);
                    lastChoice = choice;
                }
                lastChoice.addAlternative(alternativeId);
            }
        }
        return choices;
    }

    /**
     * Helper class to hold a choice id and alternatives.
     * 
     */
    private class Choice {

        private List<String> alternatives;

        private String id;

        private int currentAlternativeIndex = 0;

        public Choice(final String id) {
            this.id = id;
            alternatives = new LinkedList<String>();
        }

        public void addAlternative(final String alternativeId) {
            if (!alternatives.contains(alternativeId)) {
                alternatives.add(alternativeId);
            }
        }

        public List<String> getAlternatives() {
            return alternatives;
        }

        public String getId() {
            return id;
        }

        public int getCurrentAlternativeIndex() {
            return currentAlternativeIndex;
        }

        public void setCurrentAlternativeIndex(int currentAlternativeIndex) {
            this.currentAlternativeIndex = currentAlternativeIndex;
        }

        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("[");
            sb.append("choiceId:" + getId());
            sb.append(",");
            sb.append("alternatives:" + getAlternatives());
            sb.append(",");
            sb.append("currentAlternativeIndex:" + getCurrentAlternativeIndex());
            sb.append("]");
            return sb.toString();
        }

    }
}
